#include "std/debug.h"
#include "arch/hw_time.h"
#include "kernel/irq.h"
#include "kernel/time.h"
#include "kernel/proc.h"
#include "kernel/heap.h"

// global variable
unsigned int volatile jiffies = 0;

const static uint days_per_month_tbl[] = {
	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};

// for timer
static timer_list_t	t1 = {0, NULL},
	t2 = {0, NULL}, t3 = {0, NULL},
	t4 = {0, NULL}, t5 = {0, NULL};
static timer_list_t *tl[] = {
	&t1, &t2, &t3, &t4, &t5
};
static uint timer_jiffies = 0;

static listnode_t* get_timer_list(timer_t *timer)
{
	listnode_t *list;
	uint expire = timer->expire;
	uint t = expire - timer_jiffies;

	if (t < 256)
		list = t1.timer_list + (expire & 255);

	else if (t < (1 << (6 + 8)))
	{
		t = (expire >> 8) & ((1 << 6) - 1);
		list = t2.timer_list + t;
	}
	else if (t < (1 << (12 + 8)))
	{
		t = (expire >> (8 + 6)) & ((1 << 6) - 1);
		list = t3.timer_list + t;
	}
	else if (t < (1 << (18 + 8)))
	{
		t = (expire >> (8 + 12)) & ((1 << 6) - 1);
		list = t4.timer_list + t;
	}
	else if ((int)t < 0)
	{
		// can happen if you add a timer with expires == jiffies,
		// or you set a timer to go off in the past
		list = t1.timer_list + t1.index;
	}
	else if (t <= 0xffffffffUL)
	{
		t = (expire >> (8 + 3 * 6)) & ((1 << 6) - 1);
		list = t5.timer_list + t;
	}
	return list;
}

#define tasklet_add_timer(tmr)	\
do { \
	listnode_t* list = get_timer_list(tmr); \
	listnode_add(*list, (tmr)->list); \
} while (0)

DECLARE_PRIVATE_SPINLOCK(timer_spinlock);

bool add_timer(timer_t* timer)
{
	uint flags;
	listnode_t *list;

	// need lock
	spin_lock_irqsave(&timer_spinlock, flags);
	if (NULL == timer || !listnode_issingle(timer->list))
	{
		spin_unlock_irqrestore(&timer_spinlock, flags);
		return false;
	}

	list = get_timer_list(timer);
	listnode_add(*list, timer->list);

	// unlock
	spin_unlock_irqrestore(&timer_spinlock, flags);
	return true;
}

void remove_timer(timer_t *timer)
{
	uint flags;

	// need lock
	spin_lock_irqsave(&timer_spinlock, flags);
	if (NULL == timer || listnode_issingle(timer->list))
	{
		spin_unlock_irqrestore(&timer_spinlock, flags);
		return;
	}

	listnode_del(timer->list);

	// unlock
	spin_unlock_irqrestore(&timer_spinlock, flags);
}

static void run_timer_func(listnode_t *tmrlist)
{
	while (!listnode_isempty(*tmrlist))
	{
		listnode_t *nxt = tmrlist->next;
		timer_t *timer = list_entry(timer_t, list, nxt);
		listnode_del(*nxt);

		if (timer->on_timer)
			timer->on_timer(timer, timer->input);
	}
}

static void move_timers(timer_list_t *tv)
{
	listnode_t *head;
	listnode_t *curr;
	listnode_t *next;

	head = tv->timer_list + tv->index;
	curr = head->next;

	while (curr != head)
	{
		timer_t *tmp;
		tmp = list_entry(timer_t, list, curr);
		next = curr->next;
		listnode_del(*curr);
		tasklet_add_timer(tmp);
		curr = next;
	}
	tv->index = (tv->index + 1) & 63;
}

static void run_timer_list(void)
{
	while ((int)(jiffies - timer_jiffies) >= 0)
	{
		if (!t1.index)
		{
			uint i = 1;
			do {
				move_timers(tl[i]);
			} while (tl[i]->index == 1 && ++i < 5);
		}

		run_timer_func(t1.timer_list + t1.index);
		t1.index = (t1.index + 1) & 255;
		++timer_jiffies; 
	}
}

static void sys_time_sec_inc(void)
{
	global_time.second = (global_time.second + 1) % TIME_MAX_SEC;
	if (!global_time.second)
	{
		global_time.minute = (global_time.minute + 1) % TIME_MAX_MIN;
		if (!global_time.minute)
		{
			global_time.hour = (global_time.hour + 1) % TIME_MAX_HOUR;
			if (!global_time.hour)
			{
				uint days_per_month = days_per_month_tbl[global_time.month - 1];
				if (IS_LEAP_YEAR(global_time.year) && global_time.month == MON_FEB)
					++days_per_month;

				if (++global_time.day > days_per_month)
				{
					global_time.day = 1;
					if (++global_time.month > TIME_MAX_MON)
					{
						global_time.month = 1;
						global_time.year++;
					}
				}
			}
		}
	}
}

static void time_tasklet_handler(uint d)
{
	static uint prev_tick = 0;

	// update the system time
	if ((jiffies % HZ) < (prev_tick % HZ))
		sys_time_sec_inc();

	prev_tick = jiffies;
}

static void time_sched_tasklet_handler(uint d)
{
	time_tasklet_handler(d);
	run_timer_list();
	request_resched();
}

// tasklet structure
static tasklet_t time_tasklet =
{
	NULL,
	0, 0,
	time_tasklet_handler,
	0
};

static void sys_init_timer(void)
{
	// initialize the timer queue
	uint i;
	listnode_t* tmr_list = (listnode_t*)vmalloc(1);
	if (!tmr_list)
		bug("sys_init_timer: fail to allocate 1 page");

	for (i = 0; i < PAGE_SZ / sizeof(listnode_t); i++)
		listnode_init(tmr_list[i]);

	t1.timer_list = tmr_list;
	t2.timer_list = tmr_list + 256;
	t3.timer_list = tmr_list + 256 + 64;
	t4.timer_list = tmr_list + 256 + 64 + 64;
	t5.timer_list = tmr_list + 256 + 64 + 64 + 64;
}

void sched_enable(void)
{
	sys_init_timer();
	time_tasklet.func = time_sched_tasklet_handler;
}

static void time_handler(int irq, void* devid, regs_t* regs)
{
	++jiffies;
	tasklet_schedule(&time_tasklet);
}

static irqaction_t tm_irqaction =
{
	time_handler,
	IAF_NORELEASE | SA_SHIRQ,
	"time",
	(void*)DEVID_TIMER,
	NULL,
};

void global_init_time_module(void)
{
	int ret = _request_irq(0, &tm_irqaction);
	kassert(!ret);

	init_8253();
}

/* EOF */
